#include "includes.h"
#include "lwip/igmp.h"
#include "lwip/pbuf.h"
#include "lwip/tcp.h"
#include "lwip/udp.h"
/*
	on reception of udp packet: send to can network
	on reception of can message: send to ethernet
 */

/* bridge for can port 1, or port 2, or both */
#define ENABLE_CAN_BRIDGE_1

#define ENABLE_CAN_BRIDGE_2

/* frames be routed from(to) CAN1 to(from) eth port 1, and from(to) CAN2 to(from) eth port 2 */
ip_addr_t UDP_REMOTE_IP;
#define MULTI_IP_0 239
#define MULTI_IP_1 255
#define MULTI_IP_2 0
#define MULTI_IP_3 6
#define UDP_BRIDGE_PORT_1 6780
#define UDP_BRIDGE_PORT_2 (UDP_BRIDGE_PORT_1 + 1)

static struct udp_pcb *pcb_eth1, *pcb_eth2;

uint8_t bridge_inited = 0;

static void poll_lwip(void)
{
	if (ETH_CheckFrameReceived())
	{
		LwIP_Pkt_Handle();
	}
	LwIP_Periodic_Handle(localtime);
}

// /**
//   * @brief  CAN Rx message structure definition
//   */
// typedef struct
// {
//   uint32_t StdId;  /*!< Specifies the standard identifier.
//                         This parameter can be a value between 0 to 0x7FF. */

//   uint32_t ExtId;  /*!< Specifies the extended identifier.
//                         This parameter can be a value between 0 to 0x1FFFFFFF. */

//   uint8_t IDE;     /*!< Specifies the type of identifier for the message that
//                         will be received. This parameter can be a value of
//                         @ref CAN_identifier_type */

//   uint8_t RTR;     /*!< Specifies the type of frame for the received message.
//                         This parameter can be a value of
//                         @ref CAN_remote_transmission_request */

//   uint8_t DLC;     /*!< Specifies the length of the frame that will be received.
//                         This parameter can be a value between 0 to 8 */

//   uint8_t Data[8]; /*!< Contains the data to be received. It ranges from 0 to
//                         0xFF. */

//   uint8_t FMI;     /*!< Specifies the index of the filter the message stored in
//                         the mailbox passes through. This parameter can be a
//                         value between 0 to 0xFF */
// } CanRxMsg;

static void udp_eth1_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p,
							  struct ip_addr *addr, u16_t port)
{
#ifdef ENABLE_CAN_BRIDGE_1
	eth_side_frame *recv;
	/* receive data */
	if (p->len != sizeof(eth_side_frame))
	{
		/* incorrect length, return */
		return;
	}
	recv = (eth_side_frame *)p->payload;
	/* send as can frame */
	BSP_Can1WriteData(ntohl(recv->id) > 0x7FF ? 1 : 0, ntohl(recv->id), recv->data, ntohl(recv->len));
#endif
	pbuf_free(p);
}

static void udp_eth2_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p,
							  struct ip_addr *addr, u16_t port)
{
#ifdef ENABLE_CAN_BRIDGE_2
	eth_side_frame *recv;
	/* receive data */
	if (p->len != sizeof(eth_side_frame))
	{
		/* incorrect length, return */
		return;
	}
	recv = (eth_side_frame *)p->payload;
	/* send as can frame */
	BSP_Can2WriteData(ntohl(recv->id) > 0x7FF ? 1 : 0, ntohl(recv->id), recv->data, ntohl(recv->len));
#endif
	pbuf_free(p);
}

static void eth_init(void)
{
	err_t err;

	if (ETH_BSP_Config() != OK)
	{
		return;
	}

	/* Initilaize the LwIP stack, ip address and mac address were read from eeprom */
	LwIP_Init();

	/* create udp control block, register callback  */
	IP4_ADDR(&UDP_REMOTE_IP, MULTI_IP_0, MULTI_IP_1, MULTI_IP_2, MULTI_IP_3);
	pcb_eth1 = udp_new();
	if (pcb_eth1)
	{
		err = udp_bind(pcb_eth1, IPADDR_ANY, UDP_BRIDGE_PORT_1);
		if (err == ERR_OK)
		{
			udp_recv(pcb_eth1, udp_eth1_callback, NULL);
		}
		else
		{
			/* failed to bind */
			udp_remove(pcb_eth1);
			return;
		}
	}
	else
	{
		return;
		/* failed to alloc pcb */
	}

	/* create udp control block, register callback  */
	pcb_eth2 = udp_new();
	if (pcb_eth2)
	{
		err = udp_bind(pcb_eth2, IPADDR_ANY, UDP_BRIDGE_PORT_2);
		if (err == ERR_OK)
		{
			udp_recv(pcb_eth2, udp_eth2_callback, NULL);
		}
		else
		{
			/* failed to bind */
			udp_remove(pcb_eth2);
		}
	}
	else
	{
		/* failed to alloc pcb */
		return;
	}
	bridge_inited++;
}

static void can_init(void)
{
	vTaskDelay(100);

	BSP_CAN1_Config(500);
	BSP_CAN2_Config(500);
	bridge_inited++;
}

/**
 * @brief initialize both can and eth
 *
 */
static void bridge_init(void)
{

	/* initialize ethernet, setup udp connections, bind callback functions */
	switch (bridge_inited)
	{
		case 0:
			eth_init();
			break;
		case 1:
			/* initialize can ports, callback provided by task CAN */
			can_init();
			break;
		case 2:
			EP_Init();
			// empower
			EP_Start();
			bridge_inited++;
			break;

		default:
			break;
	}
}

/**
 * @brief callback of can1 port reception
 *
 * @param id CAN message id
 * @param pData data buffer
 * @param len length of data(byte)
 */
void receive_Can1_Frame(uint32_t id, uint8_t *pData, uint32_t len)
{
#ifdef ENABLE_CAN_BRIDGE_1
	struct pbuf *p;
	eth_side_frame ethf;

	if (len > 8) return;

	/* if link ready and allow to route for can1 */
	if ((EthInitStatus == 1) && (EthLinkStatus == 1))
	{
		ethf.id = htonl(id);
		ethf.len = htonl(len);
		memcpy(ethf.data, pData, len);
		p = pbuf_alloc(PBUF_TRANSPORT, sizeof(eth_side_frame), PBUF_RAM);
		if (p == NULL) return;
		pbuf_take(p, (const void *)&ethf, sizeof(eth_side_frame));
		udp_sendto(pcb_eth1, p, &UDP_REMOTE_IP, UDP_BRIDGE_PORT_1);
		pbuf_free(p);
	}
#endif
}

/**
 * @brief callback of can2 port reception
 *
 * @param id CAN message id
 * @param pData data buffer
 * @param len length of data(byte)
 */
void receive_Can2_Frame(uint32_t id, uint8_t *pData, uint32_t len)
{
#ifdef ENABLE_CAN_BRIDGE_2
	struct pbuf *p;
	eth_side_frame ethf;

	if (len > 8) return;

	/* if link ready and allow to route for can1 */
	if ((EthInitStatus == 1) && (EthLinkStatus == 1))
	{
		ethf.id = htonl(id);
		ethf.len = htonl(len);
		memcpy(ethf.data, pData, len);
		p = pbuf_alloc(PBUF_TRANSPORT, sizeof(eth_side_frame), PBUF_RAM);
		if (p == NULL) return;
		pbuf_take(p, (const void *)&ethf, sizeof(eth_side_frame));
		udp_sendto(pcb_eth2, p, &UDP_REMOTE_IP, UDP_BRIDGE_PORT_2);
		pbuf_free(p);
	}
#endif
}
static void Eth_Relink_WatchHandler(void)
{
	uint32_t timeout = 0;

	/* We check the linked status... */
	do
	{
		timeout++;
	} while (!(ETH_ReadPHYRegister(DP83848_PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status) &&
			 (timeout < 10));

	if (timeout == 10)
	{ /* link Down in case of timeout */
		EthLinkStatus = 0;
	}
	else
	{ /* link Up in case of timeout */
		EthLinkStatus = 1;
	}
}

void TASK_BRIDGE(void *pvParameters)
{
	TickType_t xLastWakeTime;
	const TickType_t xFrequency = 1; /* ms */
	uint16_t count = 0;
	xLastWakeTime = xTaskGetTickCount();
	while (1)
	{

		if (bridge_inited < 3)
		{
			vTaskDelay(200);
			bridge_init();
			continue;
		}

		/* can reception should be handled by task CAN, we poll only the ethernet */
		poll_lwip();
		vTaskDelayUntil(&xLastWakeTime, xFrequency);
		count++;

		if ((EthInitStatus == 1) && (EthLinkStatus == 0))
		{
			Eth_Relink_WatchHandler();
		}
	}
}

void ETH_LINK_HANDLER(void)
{
	if (EXTI_GetITStatus(ETH_LINK_EXTI_LINE) != RESET)
	{
		// check if the link interrupt has been detect
		Eth_Link_ITHandler(DP83848_PHY_ADDRESS);

		EXTI_ClearITPendingBit(ETH_LINK_EXTI_LINE); /* Clear EXTI Bits		*/
	}
}
